package cn.mjnxin.iot.connector.coder;

import java.net.InetSocketAddress;
import java.util.List;

import cn.mjnxin.iot.base.domain.ChannelData;
import cn.mjnxin.iot.base.domain.ProtocolConfig;
import cn.mjnxin.iot.base.domain.SocketData;
import cn.mjnxin.iot.base.log.LogWorker;
import cn.mjnxin.iot.base.util.CommonUtil;
import cn.mjnxin.iot.base.util.MixUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/**
 * 长度域定长多协议解码器

 * @author CLG 2023年10月10日
 * @version V1.0.0
 */
public class Gate2TerminalDecoder  extends ByteToMessageDecoder{
    private boolean isBigEndian ;//大小端
    private boolean isLengthIncludeLengthFieldLength ;//长度域长度值是否包含长度域本身长度
    private boolean isLengthIncludeOffsetLength ;//长度域长度值是否包含偏移量域本身长度
    private int     pid;//协议类型--[0,127]共128种协议
	private int     martchMode;// 识别模式 1 起始识别串+长度 2 相同起止识别串+长度+转义符  3 不同起止识别串+长度+转义符 
    private int     matchLength;//识别串对应占位字节个数
    private int     endMatchLength;//末尾识别串对应占位字节个数
    private int     lengthFieldOffset = 0;//包括beginHexVal在内长度域的偏移量(lengthFieldOffset不得小于beginHexVal字节数)
    private int     lengthFieldLength = 0;//长度域本身占位长度
    private int     extraDataLength = 0;//从长度字段到数据域之间非长度字段长度(寻址略过)
    private int     endExtraDataLength = 0;//数据域之后末尾非长度字段长度(不包含endMatchHexVal长度)
    private String  matchHexVal = "";//识别串
    private String  endMatchHexVal = "";//末尾识别串
	// 日志等级 0：无日志 1：报文长度日志 2：报文内容日志 3：所有日志
	private int logLevel = 0;
    
    /**
     * 默认起始偏移量为0
     * @param protocolConfig
     */
    public Gate2TerminalDecoder(ProtocolConfig protocolConfig) {
        this.pid = protocolConfig.getPid();
        this.isBigEndian = protocolConfig.isBigEndian();
        this.martchMode = protocolConfig.getMatchMode();
        this.matchHexVal = protocolConfig.getBeginMatchHexVal();
        this.matchLength = matchHexVal.length() / 2;
        this.endMatchHexVal = protocolConfig.getEndMatchHexVal();
        this.endMatchLength = endMatchHexVal.length() / 2;
        this.lengthFieldOffset = protocolConfig.getLengthFieldOffset();
        this.lengthFieldLength = protocolConfig.getLengthFieldLength();
        this.isLengthIncludeLengthFieldLength = protocolConfig.isLengthIncludeLengthFieldLength();
        this.isLengthIncludeOffsetLength = protocolConfig.isLengthIncludeOffsetLength();
        this.extraDataLength = protocolConfig.getExtraDataLength();
        this.endExtraDataLength = protocolConfig.getEndExtraDataLength();
		this.logLevel  = protocolConfig.getLogLevel();
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    	try {
	    	if (martchMode == 1) {
		    	if (matchLength > 0) {
		            decodeP0(ctx, in, out);
		    	}
		    	else {
		            decodeP1(ctx, in, out);
		    	}
	    	}
	    	else if(martchMode == 2 || martchMode == 3){
	    		if (matchLength > 0) {
	                decodeP2(ctx, in, out);
	    		}
	    		else {
	    			LogWorker.logForce("报文匹配模式不支持");
	    		}
	    	}
	    	else {
	    		LogWorker.logForce("报文匹配模式不支持");
	    	}
    	}
    	catch(Exception e) {
    		if (e instanceof IndexOutOfBoundsException) {
    			//防止包探测
    			ctx.channel().close();
    		}
    		throw e;
    	}
    }

	private void decodeP0(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		//需要优化读取策略，头部标记，尾部标记，有头部标记时，在查标记时要遍历
        int baseLen = lengthFieldOffset + lengthFieldLength + extraDataLength + endExtraDataLength;

        if((in.readableBytes() < baseLen)){
            return;
        }
        
        while (true) {
            int readableBytes = in.readableBytes();//重新获取可读字段
            if((readableBytes < baseLen)){
                break;
            }
            int beginReader = in.readerIndex();//重新获取开始的index

            byte[] base = new byte[baseLen];
            in.readBytes(base, 0, baseLen);
            String baseHex = MixUtil.bytes2hex(base);
            int index = baseHex.indexOf(matchHexVal);
            if (index < 0) { // 预读的一段无法识别，丢弃
                System.out.println(baseHex);
                continue;
            }
            else { // 找到识别串
                beginReader = beginReader + index /2;
                in.readerIndex(beginReader);
                readableBytes = in.readableBytes();//重新获取可读字段
            }
            
            if(readableBytes < baseLen){
                //报文不完整 还原
                in.readerIndex(beginReader);
                break;
            }
            
            ByteBuf byteBuf = CommonUtil.getByteBuf();
            
            if (lengthFieldOffset > 0) {
                for(int i = 0 ; i < lengthFieldOffset ; i++){
                    byteBuf.writeByte(in.readByte());
                }
            }
            
            //处理长度域
            int lenReader = in.readerIndex();
            int lenVal = 0;//data域长度
//            boolean isLong = false;
            switch (lengthFieldLength) {
            case 1:
                lenVal = (in.readByte() & 0x000000FF);
                break;
            case 2:
                if(isBigEndian){
                    lenVal = in.readShort();
                }else{
                    lenVal = in.readShortLE();
                }
                break;
            case 4:
                if(isBigEndian){
                    lenVal = in.readInt();
                }else{
                    lenVal = in.readIntLE();
                }
                break;
            default:
                CommonUtil.releaseByteBuf(byteBuf);
                break;
            }
            in.readerIndex(lenReader);
            in.readBytes(byteBuf, lengthFieldLength);
            if(isLengthIncludeLengthFieldLength){
                lenVal = lenVal - lengthFieldLength;
            }
            if(isLengthIncludeOffsetLength){
                lenVal = lenVal - lengthFieldOffset;
            }

            if(in.readableBytes() >= (lenVal + extraDataLength + endExtraDataLength)  && lenVal >= 0){
                byteBuf.writeBytes(in, lenVal + extraDataLength + endExtraDataLength);
                
                Channel channel = ctx.channel();
                InetSocketAddress remoteInsocket = (InetSocketAddress)channel.remoteAddress();
                String gateAddress = CommonUtil.getIpAddress(remoteInsocket);
                SocketData data = new SocketData(byteBuf);
                data.setPid(this.pid);
                CommonUtil.recieveCount.addAndGet(1);
                InetSocketAddress localInsocket = (InetSocketAddress)channel.localAddress();
                String terminalAddress = CommonUtil.getIpAddress(localInsocket);
                if (logLevel == 1) {
                	LogWorker.logForce(String.format("%d|G[%s]->T[%s]=", this.pid, gateAddress, terminalAddress, data.getByteBuf().readableBytes()));
                }
                else if (logLevel >= 2) {
                	LogWorker.logForce(String.format("%d|G[%s]->T[%s]=", this.pid, gateAddress, terminalAddress), data.getByteBuf());
                }
                ChannelData channelData =  new ChannelData(gateAddress, terminalAddress, this.pid, this.logLevel, data);
                out.add(channelData);

                continue;
            }else{
                //报文不完整 还原
                in.readerIndex(beginReader);
                CommonUtil.releaseByteBuf(byteBuf);
                break;
            }
        }
	}

    private void decodeP1(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        //需要优化读取策略，头部标记，尾部标记，有头部标记时，在查标记时要遍历
        int baseLen = lengthFieldOffset + lengthFieldLength + extraDataLength + endExtraDataLength;
        if(in.readableBytes() < baseLen){
            return;
        }
        int beginReader;
        while (true) {
            int readableBytes = in.readableBytes();
            if(readableBytes < baseLen){
            	break;
            }
            // 获取包头开始的index
            beginReader = in.readerIndex();
            
            ByteBuf byteBuf = CommonUtil.getByteBuf();
            
            if (lengthFieldOffset > 0) {
                for(int i = 0 ; i < lengthFieldOffset ; i++){
                    byteBuf.writeByte(in.readByte());
                }
            }
            
            //处理长度域
            int lenReader = in.readerIndex();
            int lenVal = 0;//data域长度
            switch (lengthFieldLength) {
            case 1:
                lenVal = (in.readByte() & 0x000000FF);
                break;
            case 2:
                if(isBigEndian){
                    lenVal = in.readShort();
                }else{
                    lenVal = in.readShortLE();
                }
                break;
            case 4:
                if(isBigEndian){
                    lenVal = in.readInt();
                }else{
                    lenVal = in.readIntLE();
                }
                break;
            default:
                CommonUtil.releaseByteBuf(byteBuf);
                break;
            }
            in.readerIndex(lenReader);
            in.readBytes(byteBuf, lengthFieldLength);
            if(isLengthIncludeLengthFieldLength){
                lenVal = lenVal - lengthFieldLength;
            }
            if(isLengthIncludeOffsetLength){
                lenVal = lenVal - lengthFieldOffset;
            }
//          if(isLenthIncludeExtraDataLenth){
//          lenVal = lenVal - extraDataLenth;
//      }
            
            if(in.readableBytes() >= (lenVal + extraDataLength + endExtraDataLength)  && lenVal >= 0){
//                for(int i = 0; i < (lenVal + extraDataLenth) ; i++ ){
//                    byteBuf.writeByte(in.readByte());
//                }
                byteBuf.writeBytes(in, lenVal + extraDataLength + endExtraDataLength);
                
                Channel channel = ctx.channel();
                InetSocketAddress remoteInsocket = (InetSocketAddress)channel.remoteAddress();
                String gateAddress = CommonUtil.getIpAddress(remoteInsocket);
                SocketData data = new SocketData(byteBuf);
                data.setPid(this.pid);
                CommonUtil.recieveCount.addAndGet(1);
                InetSocketAddress localInsocket = (InetSocketAddress)channel.localAddress();
                String terminalAddress = CommonUtil.getIpAddress(localInsocket);
                if (logLevel == 1) {
                	LogWorker.logForce(String.format("%d|G[%s]->T[%s]=", this.pid, gateAddress, terminalAddress, data.getByteBuf().readableBytes()));
                }
                else if (logLevel >= 2) {
                	LogWorker.logForce(String.format("%d|G[%s]->T[%s]=", this.pid, gateAddress, terminalAddress), data.getByteBuf());
                }
                ChannelData channelData =  new ChannelData(gateAddress, terminalAddress, this.pid, this.logLevel, data);
                out.add(channelData);
                continue;
            }else{
                //报文不完整 还原
                in.readerIndex(beginReader);
                CommonUtil.releaseByteBuf(byteBuf);
                break;
            }
        }
    }

    //识别串的识别模式
	private void decodeP2(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		//需要优化读取策略，头部标记，尾部标记，有头部标记时，在查标记时要遍历
        int baseLen = lengthFieldOffset + lengthFieldLength + extraDataLength + endExtraDataLength;

        if((in.readableBytes() <  matchLength)){
            return;
        }
        
        while (true) {
            int readableBytes = in.readableBytes();//重新获取可读字段
            if((readableBytes < baseLen + endMatchLength)){
                break;
            }
            int beginReader = in.readerIndex();//重新获取开始的index

            byte[] base = new byte[baseLen];
            in.readBytes(base, 0, baseLen);
            String baseHex = MixUtil.bytes2hex(base);
            int index = baseHex.indexOf(matchHexVal);
            if (index < 0) { // 预读的一段无法识别，丢弃
                System.out.println(baseHex);
                continue;
            }
            else { // 找到识别串
                beginReader = beginReader + index /2; //识别串起始位
                //越过一段字节再查找尾部识别串
                in.readerIndex(beginReader + lengthFieldOffset + lengthFieldLength + extraDataLength + endExtraDataLength);
                readableBytes = in.readableBytes();//重新获取可读字段
            }
            
            byte[] allBuf = null;
            while (readableBytes >= endMatchLength) {
                int endReader = in.readerIndex();//重新获取结束的index
                byte[] end = new byte[endMatchLength];
                in.readBytes(end, 0, endMatchLength);
                String endHex = MixUtil.bytes2hex(end);
                if (!endHex.equals(endMatchHexVal)) { // 预读的一段无法识别
                	readableBytes = in.readableBytes();//重新获取可读字段
                    continue;
                }
                else { // 找到识别串
                	endReader = in.readerIndex();//重新获取结束的index
                }
                in.readerIndex(beginReader);
                allBuf = new byte[endReader - beginReader];
                in.readBytes(allBuf, 0, allBuf.length);
                break;
            }
            if (allBuf != null) {
                Channel channel = ctx.channel();
                InetSocketAddress remoteInsocket = (InetSocketAddress)channel.remoteAddress();
                String gateAddress = CommonUtil.getIpAddress(remoteInsocket);
                SocketData data = new SocketData(Unpooled.wrappedBuffer(allBuf));
                data.setPid(this.pid);
                CommonUtil.recieveCount.addAndGet(1);
                InetSocketAddress localInsocket = (InetSocketAddress)channel.localAddress();
                String terminalAddress = CommonUtil.getIpAddress(localInsocket);
                if (logLevel == 1) {
                	LogWorker.logForce(String.format("%d|G[%s]->T[%s]=", this.pid, gateAddress, terminalAddress, data.getByteBuf().readableBytes()));
                }
                else if (logLevel >= 2) {
                	LogWorker.logForce(String.format("%d|G[%s]->T[%s]=", this.pid, gateAddress, terminalAddress), data.getByteBuf());
                }
                ChannelData channelData =  new ChannelData(gateAddress, terminalAddress, this.pid, this.logLevel, data);
                out.add(channelData);
                continue;
            }
            else {
                //报文不完整 还原
                in.readerIndex(beginReader);
                break;
            }
        }
	}

    public String getBeginHexVal() {
        return matchHexVal;
    }

	public Integer getPid() {
		return pid;
	}
}
